//go:build !windows

package collector

import (
	"encoding/xml"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"sort"
	"strings"
	"time"

	"sysinfo/internal/types"
)

// collectRecentFiles (Linux & macOS)
// Linux: 解析 ~/.local/share/recently-used.xbel (freedesktop XBEL)
// macOS: 扫描 ~/Library/Application Support/com.apple.sharedfilelist/*.sfl2 中的 file:// URL（近似解析）
func collectRecentFiles(limit int) ([]types.RecentFile, error) {
	if limit <= 0 {
		limit = 15
	}
	switch runtime.GOOS {
	case "linux":
		return collectRecentFilesLinux(limit)
	case "darwin":
		return collectRecentFilesDarwin(limit)
	default:
		return []types.RecentFile{}, nil
	}
}

// ===== Linux =====
type xbelBookmark struct {
	Href     string `xml:"href,attr"`
	Added    string `xml:"added,attr"`
	Modified string `xml:"modified,attr"`
	Visited  string `xml:"visited,attr"`
	Title    string `xml:"title"`
}

func collectRecentFilesLinux(limit int) ([]types.RecentFile, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return []types.RecentFile{}, nil
	}
	xbelPath := filepath.Join(home, ".local", "share", "recently-used.xbel")
	f, err := os.Open(xbelPath)
	if err != nil {
		return []types.RecentFile{}, nil
	}
	defer f.Close()

	dec := xml.NewDecoder(f)
	rf := make([]types.RecentFile, 0, limit)
	tmp := make([]types.RecentFile, 0, 128)

	for {
		tok, err := dec.Token()
		if err != nil {
			break
		}
		se, ok := tok.(xml.StartElement)
		if !ok {
			continue
		}
		if se.Name.Local != "bookmark" {
			continue
		}

		var b xbelBookmark
		if err := dec.DecodeElement(&b, &se); err != nil {
			continue
		}
		if b.Href == "" || !strings.HasPrefix(b.Href, "file://") {
			continue
		}

		u, err := url.Parse(b.Href)
		if err != nil {
			continue
		}
		p, _ := url.PathUnescape(u.Path)
		if p == "" {
			continue
		}
		disp := b.Title
		if disp == "" {
			disp = filepath.Base(p)
		}

		ts := parseRecentTime(b.Visited)
		if ts.IsZero() {
			ts = parseRecentTime(b.Modified)
		}
		if ts.IsZero() {
			ts = parseRecentTime(b.Added)
		}
		if ts.IsZero() {
			// fallback to file mtime if exists
			if fi, err := os.Stat(p); err == nil {
				ts = fi.ModTime()
			}
		}

		tmp = append(tmp, types.RecentFile{Path: p, DisplayName: disp, OpenedTime: ts})
	}

	// sort by time desc, take limit
	sort.Slice(tmp, func(i, j int) bool { return tmp[i].OpenedTime.After(tmp[j].OpenedTime) })
	if len(tmp) > limit {
		rf = tmp[:limit]
	} else {
		rf = tmp
	}
	return rf, nil
}

// ===== macOS =====
func collectRecentFilesDarwin(limit int) ([]types.RecentFile, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return []types.RecentFile{}, nil
	}
	dir := filepath.Join(home, "Library", "Application Support", "com.apple.sharedfilelist")
	entries, err := os.ReadDir(dir)
	if err != nil {
		return []types.RecentFile{}, nil
	}

	// Look for file:// URLs inside .sfl2 binary. This is a best-effort heuristic.
	re := regexp.MustCompile(`file://[^\x00\s]+`)
	uniq := make(map[string]types.RecentFile)
	list := make([]types.RecentFile, 0, limit*2)

	for _, e := range entries {
		name := e.Name()
		if !strings.HasSuffix(name, ".sfl2") {
			continue
		}
		p := filepath.Join(dir, name)
		b, err := os.ReadFile(p)
		if err != nil {
			continue
		}
		matches := re.FindAllString(string(b), -1)
		var mt time.Time
		if fi, err := os.Stat(p); err == nil {
			mt = fi.ModTime()
		} else {
			mt = time.Now()
		}

		for _, s := range matches {
			u, err := url.Parse(s)
			if err != nil {
				continue
			}
			fp, _ := url.PathUnescape(u.Path)
			if fp == "" {
				continue
			}
			if _, exists := uniq[fp]; exists {
				continue
			}
			disp := filepath.Base(fp)
			rf := types.RecentFile{Path: fp, DisplayName: disp, OpenedTime: mt}
			uniq[fp] = rf
			list = append(list, rf)
		}
	}

	sort.Slice(list, func(i, j int) bool { return list[i].OpenedTime.After(list[j].OpenedTime) })
	if len(list) > limit {
		list = list[:limit]
	}
	return list, nil
}

// parseRecentTime tries multiple formats commonly seen in XBEL
func parseRecentTime(s string) time.Time {
	s = strings.TrimSpace(s)
	if s == "" {
		return time.Time{}
	}
	// Try RFC3339
	if t, err := time.Parse(time.RFC3339, s); err == nil {
		return t
	}
	// Try without timezone
	if t, err := time.Parse("2006-01-02T15:04:05", s); err == nil {
		return t
	}
	// Try space-separated
	if t, err := time.Parse("2006-01-02 15:04:05", s); err == nil {
		return t
	}
	return time.Time{}
}
